/* Copyright 2014 Jan Wolter */

#include "stack.h"
#include "tableau.h"
#include "found.h"
#include "course.h"
#include "stock.h"
#include "solve.h"
#include "game.h"

#define COMPRESS_STATE

char *gamename= "Black Hole";
char *gameopt= "IPR";
Stack *gathered= NULL;
Stack *temp= NULL;

int nplayed= 0;
int nwon= 0;
int nabandoned= 0;
int cardsleft= 0;
int maxoff;
int npruned;

int invert= 0;
int byrow= 0;
int prune= 0;

void setopt(char c)
{
    switch (c)
    {
	case 'I': invert= 1; break;
	case 'R': byrow= 1; break;	/* Deal by row instead of col */
	case 'P': prune= 1; break;	/* Prune some moves leading to
					   unwinnable states */
    }
}


/* This is the main program for setting up the current game */
void exec(int gather, int arif, int over, int cut)
{
    if (prune)
    {
	/* The Rankdata array will count how many cards of each type are still
	 * in the game, that is, have not yet been moved to the foundation.
	 */
	rankdata= (int *)malloc(13*sizeof(int));
	for (int i= 0; i < 13; i++)
	    rankdata[i]= 4;
	npruned= 0;
    }

    /* Build and shuffle the deck */
    Stack *shuf= mixcards(arif, over, cut);

    /* Create the waste/foundation thingy */
    makeCourse(1,1,NULL);

    /* Put an Ace on the foundation (unless we are inverted) */
    Card *c= extractCard(shuf,1,0);
    if (prune) rankdata[0]--;
    if (!invert && c != NULL)
    {
	c->faceup= 1;
	pushStack(stacks+course[0].id, c);
    }

    /* Create the tableau */
    for (int i= 1; i <= 17; i++)
	makeTableau(byrow ? 0 : 3, FLIP_NONE, shuf,
	    S_NONE, 0, S_NONE, 0, 1, F_NONE, BREAK_NOWHERE, 0);

    if (byrow)
    {
	for (int j= 0; j < 3; j++)
	    for (int i= 0; i < 17; i++)
	    {
		Card *c= popStack(shuf);
		if (c == NULL) break;
		pushStack(stacks+tableau[i].id, c);
	    }
    }

    if (invert)
    {
	/* Flip each tableau stack upside down */
	for (int i= 0; i < 17; i++)
	{
	    Stack *s= stacks + tableau[i].id;
	    Card *c= s->c[0];
	    s->c[0]= s->c[2];
	    s->c[2]= c;
	}
    }

    freeStack(shuf);

    if (verbose > 1) printState(stdout, stacks);

    maxoff= 0;
    int win= solve(gather,0);

    if (verbose > 0) printf("maxoff=%d npruned=%d\n",maxoff,npruned);
    nplayed++;
    cardsleft+= maxoff;
    if (win == 1)
	nwon++;
    else if (win == 2)
	nabandoned++;

    cleanFound();
    cleanTableau();
    cleanCourse();
    cleanStacks();
    cleanVisited();
}

void gatherAll()
{
    if (gathered == NULL)
	gathered= allocStack(ncard);
    else
	gathered->n= 0;

    gatherTableau(gathered);
    gatherCourse(gathered,0);
    if (gathered->n != ncard)
    {
	printf("Gather Failed - found %d cards\n",gathered->n);
	exit(1);
    }
}

/* How good is a move? Returns 0 if the move is completely safe and should
 * be made immediately without counting this position a choice point for
 * backtracking. Positive values are lower for better moves. Negative values
 * mean the move should not be done at all.
 */
int rateMove(Stack *src, int index1, int len, Stack *dst)
{
    CourseStack *c= course + dst->tid;
    int dirscore= 30;
    int newpip= src->c[index1]->n;

    /* Don't consider removing cards from one-card stacks if there are
     * multi-card stacks with a card of the same rank on top, or if there
     * is another one-card stack with a card of the same rank and a smaller
     * index number.
     */
    if (index1 == 0)
    {
	for (int k= 0; k < ntableau; k++)
	{
	    Stack *s= stacks + tableau[k].id;
	    if (src == s || s->n == 0 || topStack(s)->n != newpip)
		continue;
	    if (s->n > 1 || s < src)
		return -1;
	}
    }

    if (prune && rankdata[newpip-1] == 1)
    {
	/* If this is creating an empty rank, that is not adjacent to
	 * another empty rank, but there are other empty ranks, then this
	 * disconnects the ranks and cannot lead to a win.
	 */
	int nprev= rankdata[(newpip+11)%13];
	int nnext= rankdata[newpip%13];
	if (nprev > 0 && nnext > 0)
	    for (int i= 1; i < 11; i++)
		if (rankdata[(newpip + i) % 13] == 0)
		{
		    npruned++;
		    return -1;
		}

	/* In inverted games we need to end with a King or a Two.
	 * If we are not playing the last card, then we cannot use
	 * up both the Kings and the Twos */
	if (invert && dst->n < 50)
	{
	    if ((newpip == 13 && rankdata[1] == 0) ||
		(newpip == 0 && rankdata[12] == 0))
	    {
		npruned++;
		return -1;
	    }
	}
    }

    /* For first card prefer 2's and K's
     * (this only occurs if we are inverted) */
    if (dst->n == 0)
    {
	return (newpip == 2 || newpip == 13) ? 1 : 2;
    }

    /* Prefer NOT making a direction change */
    if (dst->n > 2)
    {
	int toppip= dst->c[dst->n-1]->n;
	int nxtpip= dst->c[dst->n-2]->n;

	int olddir= (toppip-nxtpip + 13)%13;
	int newdir= (newpip-toppip + 13)%13;
	if (olddir != newdir) dirscore= 60;
    }

    /* Otherwise, prefer cards from taller stacks */
    return dirscore - src->n;
}

void printState(FILE *f, Stack *stks)
{
    printTableau(f, stks);
    printCourse(f, stks);
}

int victory()
{
    int off= nOff();
    if (off > maxoff) maxoff= off;
    if (off < ncard - 1) return 0;
    if (!invert) return (off == ncard);
    /* If we are playing an inverted game, we must finish with a K or a 2
     * and we only need ncard-1 cards off */
    Card *c= topStack(stacks+course[0].id);
    return (c->n == 2 || c->n == 13);
}

/* Return a quality rating for the current solution. Bigger is better. Usually
 * it's just cards off. This is used to decide which solution to report in
 * cases where we never win.
 */
int quality()
{
    int off= nOff();
    if (off >= ncard-1 && victory()) off++;
    return off;
}

void summary()
{
    printf("Played:    %d games\n",nplayed);
    printf("Won:       %d games (%.2f%)\n",nwon,(float)nwon*100/nplayed);
    printf("Abandoned: %d games (%.2f%)\n",nabandoned,(float)nabandoned*100/nplayed);
    printf("Cards Off: %.2f\n",(float)cardsleft/nplayed);
}

/* Return a string that uniquely describes the current state. */
char *currstate()
{
    /* card count for each tableau stack, and top card on course */
    char buf[ntableau + 10], *p= buf;

    /* Numbers of cards in each tableau pile */
    /* We add one to each count because we don't want any zeros */
#ifdef COMPRESS_STATE
    /* Assumes that all stack sizes are between 0 and 3 */
    int k;
    for (k= 0; k < ntableau - 2; k+= 3)
	*(p++)= (stacks[tableau[k].id].n << 1) +
	        (stacks[tableau[k+1].id].n << 3) +
	        (stacks[tableau[k+2].id].n << 5) + 1;
    if (k == ntableau - 2)
	*(p++)= (stacks[tableau[k].id].n << 1) +
	        (stacks[tableau[k+1].id].n << 3) + 1;
    else if (k == ntableau - 1)
	*(p++)= stacks[tableau[k].id].n + 1;
#else
    for (int k= 0; k < ntableau; k++)
	*(p++)= stacks[tableau[k].id].n + 1;
#endif

    /* Pip value of top card on course */
    Stack *fs= stacks + course[0].id;
    if (fs->n > 0)
	*(p++)= fs->c[fs->n-1]->n;

    *p= '\0';

    return strdup(buf);
}

